import { EventEmitter } from '../base/events'
import { PartBase } from './parts/PartBase'
import { StudFigure } from '../graph/StudFigure'
import { isGPIOPin, isSeiralUsage, Usage } from './StudUsage'
import { Bus } from "./conn/Bus"
import { Wire } from "./conn/Wire"
import CablePort from "./conn/CablePort"
import { PartHost,PartSlaver } from "./parts/PartBase"



class AllSerialUsagesClass extends Array<Usage> {

    constructor() {
        super(
            Usage.uartmosi,
            Usage.uartmiso,
            Usage.i2cscl,
            Usage.i2csda,
            Usage.spisck,
            Usage.spimosi,
            Usage.spimiso
        )
    }

    isUART(usage: Usage): boolean {
        return usage.substr(0,5) == "uart."
    }
    isI2C (usage: Usage): boolean {
        return usage.substr(0,4) == "i2c."
    }
    isSPI (usage: Usage): boolean {
        return usage.substr(0,4) == "spi."
    }
}
export const AllSerialUsages = new AllSerialUsagesClass()


class AllGPIOUsagesClass extends Array<Usage> {
    constructor() {
        super(
            Usage.adc ,
            Usage.gpioinput ,
            Usage.gpiooutput ,
            Usage.pwm ,
        )
    }

    canbeGPIO(optUsages: Usage[]) {
        for(var u of optUsages) {
            if(this.includes(u))
                return true
        }
        return false
    }
}
export const AllGPIOUsages = new AllGPIOUsagesClass

export class PWMTimerSetting {

    constructor() {}

    public frequency = 50       // 频率
    public resolution = 16      // 分辨率
    
}

export class PWMState {
    constructor(public stud:Stud) {}

    // 0-1
    private _value: number = 0
    public get value() {
        return this._value
    }
    public set value(v) {
        this._value = v
        this.stud.emit("pwm.value.set", this.stud, v)
    }
    public changeValue(v: number) {
        this._value = v
    }

    setting: PWMTimerSetting|null = null  
}

export enum GPIOMode {    
    analog = "analog " ,
    input = "input" ,
    input_pullup = "input-pullup" ,
    input_pulldown = "input-pulldown" ,
    output = "output" ,
    // opendrain = "opendrain" ,
    // opendrain_pullup = "opendrain_pullup" ,
    // af_output = "af_output" ,                   // 复用输出
    // af_opendrain  = "af_opendrain " ,           // 复用开漏输出
    
}

export class GPIOState {
    constructor(public stud: Stud) {}

    // value 用于输出，输入用 changeValue()
    private _value:number = 0
    public get value():number { return this._value }
    public set value(v: number) {
        if( this._value != v) {
            let old = this._value
            this._value = v
            // Vue.set(this, "_value", v)
            this.stud.emit("output.set", this.stud, v, old)
        }
    }
    public changeValue(v: number) {
        // Vue.set(this, "_value", v)
        this._value = v
        this.stud.emit("input.changed", this.stud, v)
    }
    
    private _mode: GPIOMode = GPIOMode.input
    public get mode(): GPIOMode {return this._mode}
    public set mode(m: GPIOMode) {
        if( this._mode != m) {
            let old = this._mode
            // Vue.set(this, "_mode", m)
            this._mode = m
            if( this.enableModeChangedEvent )
                this.stud.emit("mode.changed", this.stud, m, old)
        }
    }
    public setModeWithoutEvent(mode: GPIOMode) {
        let origin = this.enableModeChangedEvent
        this.enableModeChangedEvent = false

        this.mode = mode
        
        this.enableModeChangedEvent = origin
    }

    name(): null|string {
        if(!this.stud.info || !this.stud.info.name || !this.stud.info.name.match)
            return null
        if(!this.stud.info.name.match(/^\d+$/)){
            return null
        }
        return this.stud.info.name
    }
    
    private enableModeChangedEvent: boolean = true
}

/**
 * events:
 *   usage.changed       // 用途改变
 *   mode.changed        // gpio用途下，模式改变
 *   output.set          // 输出模式下，主动设置
 *   input.change        // 输入模式下，输入值改变
 *   bus.join            // 加入总线
 *   bus.leave           // 离开总线
 */
export class Stud extends EventEmitter {

    public figure?: StudFigure

    private _usage: Usage = Usage.none
    public get usage (): Usage { return this._usage }
    public set usage (value) {
        if(this._usage!=value) {
            let ori = this._usage
            this._usage = value
            this.emit("usage.changed", this, value, ori)
        }
    }
    
    public optUsages: Usage[] = []

    public placement: number[] = [-1, -1]

    public bus?: Bus
    public wires: Wire[] = []

    public cablePort: CablePort|null = null

    // 用于 串口 用途
    // public serial?: Cable

    // 用于 pwm 用途
    public pwm = new PWMState(this)
    // 用于 gpio 用途
    public gpio: GPIOState = new GPIOState(this)

    public destroyed = false
    
    constructor(public info: any, public readonly part: PartBase) {
        super()

        // 备选用途
        if(info.usages) {
            info.usages.forEach( (func: Usage)=>this.optUsages.push(func) )
        }
        
        // 默认用途
        if(info.usage) {
            this.usage = info.usage

            // if( part instanceof PartSlaver ){
            //     let cableClazz: CableClass|false = isSeiralUsage(info.usage)
            //     if(cableClazz) {
            //         part.registerCableStud(cableClazz, this)
            //     }
            // }
        }
        else {
            this.usage = Usage.none
        }

        this.on("bus.join",(stud: any, bus:Bus)=>{
            if(this.part instanceof PartHost) {
                this.hostStudAutoSetUsage(bus)
            }
            else{
                this.slaveStudAutoSetUsage(bus)
            }
        })
    }

    // 做为主机stud加入到总线中
    private hostStudAutoSetUsage(bus: Bus) {
        bus.allStuds(stud=>{
            if(stud.usage!=Usage.none) {
                stud.slaveStudAutoSetUsage(bus)
                return false
            }
        }, [this])
    }

    // 做为外设stud加入到总线中
    private slaveStudAutoSetUsage(bus: Bus) {
        let hostStud = bus.findHostStud()
        if(!hostStud)
            return
        if(this.usage==Usage.none){
            return
        }
        if(hostStud.usage==this.usage){
            return
        }
        if( ! hostStud.optUsages.includes(this.usage)){
            return
        }
        hostStud.usage = this.usage

        // 总线分配
        // if(this.cablePort && hostStud.part instanceof PartHost) {
        //     hostStud.part.allocCable(hostStud, this)
        // }
    }

    public destroy () {
        for(let w of this.wires) {
            if(!w.destroyed){
                this.part.model.disconnectWire(w)
            }
        }
        this.wires.splice(0, this.wires.length)

        delete this.bus
        // delete this.figure

        this.destroyed = true
    }

    // public autoSetUsageViaWire(other: Stud, wire: Wire) {
    //     if(this.usage==Usage.none && other.usage!=Usage.none && this.optUsages.includes(other.usage)) {
    //         // 设备 和 主机 的 i/o 用途相对
    //         if( this.part instanceof PartHost && other.part instanceof PartSlaver && (other.usage==Usage.gpioinput || other.usage==Usage.gpiooutput) ) {
    //             this.usage = other.usage==Usage.gpioinput? Usage.gpiooutput: Usage.gpioinput
    //         }
    //         else {
    //             this.usage = other.usage
    //         }
    //     }
    // }

    public serialize(): any {

        if(this.usage==Usage.none)
            return

        let doc: any = {
            name: this.info.name ,
            usage: this.usage ,
        }

        switch(this.usage) {
            case Usage.gpiooutput:
                doc.gpio = {
                    value: this.gpio.value? 1: 0 ,
                    mode: this.gpio.mode
                }
                break

            case Usage.gpioinput:
                doc.gpio = {
                    mode: this.gpio.mode
                }
                break

            case Usage.pwm :
                doc.pwm = {
                    value: this.pwm.value || 0 ,
                    frequency: this.pwm.setting?.frequency
                }
                break

            case Usage.adc:
                break
            
            default :
                return null

        }
        
        return doc
    }

    unserialize(json: any) {
        if(json.usage){
            this.usage = json.usage
        }
        if(json.pwm) {
            this.pwm.changeValue(json.pwm.value||0)
            this.pwm.setting && (this.pwm.setting.frequency = json.pwm?.frequency)
        }
        if(json.gpio) {
            this.gpio.changeValue(json.gpio.value?1:0)
            json.gpio.mode && (this.gpio.mode = json.gpio.mode)
        }
    }
}